package org.apache.jmeter.protocol.java.sampler;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;

import junit.framework.AssertionFailedError;
import junit.framework.Protectable;
import junit.framework.TestCase;
import junit.framework.TestFailure;
import junit.framework.TestResult;

//import org.apache.jmeter.protocol.java.sampler.JUnitSampler.AnnotatedTestCase;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Samplers;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
import org.junit.Test;
import org.junit.Test.None;

public class JunitParamSampler extends JUnitSampler implements Samplers{

	private static final Logger log = LoggingManager.getLoggerForClass();
	private static final long serialVersionUID = 240L; // Remember to change this when the class changes ...
	
	private static final String CLASSNAME = "junitParamSampler.classname";
	private static final String CONSTRUCTORSTRING = "junitParamsampler.constructorstring";
	
	//为适应参数化junit运行增加的变量
	private transient Object testObject;
	private transient ArrayList<Object> testObjectList;
	private transient int instanceNum;
	private transient int instanceIndex;
	
	private transient TestCase testCase;
	private transient ArrayList<TestCase> testCases = new ArrayList<TestCase>();
	
	private transient String className;
	private transient String methodName;
	
    
    private transient Protectable protectable;
	
	
	public JunitParamSampler(){
		super();
	}
	
	@Override
	public void setClassname(String classname)
    {
        setProperty(CLASSNAME, classname);
    }
	
	@Override
	public String getClassname()
    {
        return getPropertyAsString(CLASSNAME);
    }
	
	@Override
	public void setConstructorString(String constr)
    {
        setProperty(CONSTRUCTORSTRING,constr);
    }

    @Override
    public String getConstructorString()
    {
        return getPropertyAsString(CONSTRUCTORSTRING);
    }
    
    
    
//    @Override
    public ArrayList<SampleResult> samples(Entry entry) {
		// 由于初始化多个case，返回值页会有多个，此处对sample返回值的处理有修改
		ArrayList<SampleResult> sresultList = new ArrayList<SampleResult>();
//		this.instanceNum = getInstanceNum(className);
		// 初始化instanceIndex
		this.instanceIndex = 0;
		// 增加case处理循环
		for(TestCase each:testCases){
			SampleResult sresult = new SampleResult();
			sresult.setSampleLabel(getName());// Bug 41522 - don't use rlabel here
			//标记threadName为case名
			sresult.setThreadName(each.getName());
			sresult.setSamplerData(className + "." + methodName);
			sresult.setDataType(SampleResult.TEXT);
			// Assume success
			sresult.setSuccessful(true);
			sresult.setResponseMessage(getSuccess());
			sresult.setResponseCode(getSuccessCode());
			// create a new TestResult
			TestResult tr = new TestResult();
			final TestCase theClazz = each;
		if (each != null) {
			
			try {
				if (setUpMethod != null) {
					setUpMethod.invoke(this.testObjectList.get(instanceIndex), new Object[0]);
				}
				sresult.sampleStart();
				tr.startTest(each);
				// Do not use TestCase.run(TestResult) method, since it will
				// call setUp and tearDown. Doing that will result in calling
				// the setUp and tearDown method twice and the elapsed time
				// will include setup and teardown.
				tr.runProtected(theClazz, protectable);
				tr.endTest(each);
				sresult.sampleEnd();
				if (tearDownMethod != null) {
					tearDownMethod.invoke(testObjectList.get(instanceIndex), new Object[0]);
				}
			} catch (InvocationTargetException e) {
				Throwable cause = e.getCause();
				if (cause instanceof AssertionFailedError) {
					tr.addFailure(theClazz, (AssertionFailedError) cause);
				} else if (cause instanceof AssertionError) {
					// Convert JUnit4 failure to Junit3 style
					AssertionFailedError afe = new AssertionFailedError(
							cause.toString());
					// copy the original stack trace
					afe.setStackTrace(cause.getStackTrace());
					tr.addFailure(theClazz, afe);
				} else if (cause != null) {
					tr.addError(theClazz, cause);
				} else {
					tr.addError(theClazz, e);
				}
			} catch (IllegalAccessException e) {
				tr.addError(theClazz, e);
			} catch (IllegalArgumentException e) {
				tr.addError(theClazz, e);
			}
			if (!tr.wasSuccessful()) {
				sresult.setSuccessful(false);
				StringBuilder buf = new StringBuilder();
				StringBuilder buftrace = new StringBuilder();
				Enumeration<TestFailure> en;
				if (getAppendError()) {
					en = tr.failures();
					if (en.hasMoreElements()) {
						sresult.setResponseCode(getFailureCode());
						buf.append(getFailure());
						buf.append("\n");
					}
					while (en.hasMoreElements()) {
						TestFailure item = en.nextElement();
						buf.append("Failure -- ");
						buf.append(item.toString());
						buf.append("\n");
						buftrace.append("Failure -- ");
						buftrace.append(item.toString());
						buftrace.append("\n");
						buftrace.append("Trace -- ");
						buftrace.append(item.trace());
					}
					en = tr.errors();
					if (en.hasMoreElements()) {
						sresult.setResponseCode(getErrorCode());
						buf.append(getError());
						buf.append("\n");
					}
					while (en.hasMoreElements()) {
						TestFailure item = en.nextElement();
						buf.append("Error -- ");
						buf.append(item.toString());
						buf.append("\n");
						buftrace.append("Error -- ");
						buftrace.append(item.toString());
						buftrace.append("\n");
						buftrace.append("Trace -- ");
						buftrace.append(item.trace());
					}
				}
				sresult.setResponseMessage(buf.toString());
				sresult.setResponseData(buftrace.toString(), null);
			}
		} else {
			// we should log a warning, but allow the test to keep running
			sresult.setSuccessful(false);
			// this should be externalized to the properties
			sresult.setResponseMessage("failed to create an instance of the class");
			sresult.setResponseCode(getErrorCode());
		}
		sresultList.add(sresult);
		this.instanceIndex++;
		}
		return sresultList;
	
	}
    
    /**
	 * If the method is not able to create a new instance of the class, it
	 * returns null and logs all the exceptions at warning level.
	 */
	// 此处有修改，增加instanceIndex，获取list中对应的值
	private static Object getClassInstance(String className, String label, int instanceIndex) {
		Object testclass = null;
		ArrayList<Object> testclasses = new ArrayList<Object>();
		if (className != null) {
			Constructor<?> con = null;
			Constructor<?> strCon = null;
			Class<?> theclazz = null;
			Object[] strParams = null;
			Object[] params = null;
			try {
				// 注意，通过线程装载class时，不会默认初始化该类
				theclazz = Thread.currentThread().getContextClassLoader()
						.loadClass(className.trim());
			} catch (ClassNotFoundException e) {
				log.warn("ClassNotFoundException:: " + e.getMessage());
			}
			if (theclazz != null) {
				// first we see if the class declares a string
				// constructor. if it is doesn't we look for
				// empty constructor.
				try {
					strCon = theclazz
							.getDeclaredConstructor(new Class[] { String.class });
					// we have to check and make sure the constructor is
					// accessible. if we didn't it would throw an exception
					// and cause a NPE.
					if (label == null || label.length() == 0) {
						label = className;
					}
					if (strCon.getModifiers() == Modifier.PUBLIC) {
						strParams = new Object[] { label };
					} else {
						strCon = null;
					}
				} catch (NoSuchMethodException e) {
					log.info("String constructor:: " + e.getMessage());
				}
				try {
					
					// 此处有修改，原代码获取默认无参数的构造函数，此处修改为获取所有构造函数
					Constructor<?>[] cons = theclazz.getDeclaredConstructors();
					// TODO 这里用了硬编码，itest使用没问题，但是扩展性不好，需要改进
					con = cons[0];
					Class<?>[] types = con.getParameterTypes();
					
					// 以下均有修改，获取parameter的方法
					Method[] theMethods = theclazz.getMethods();
					for (Method m : theMethods) {
						// 根据返回类型来判断是否是参数值获取的方法，与JUnit的参数化实现相关	
						if (m.getReturnType().isAssignableFrom(
								java.util.Collection.class)) {
							Collection<Object[]> returnParam = (Collection<Object[]>) m.invoke(null);
							params = (Object[]) returnParam.toArray()[instanceIndex];
							break;
						}
					}
				} catch (Exception e) {
					log.info("Empty constructor:: " + e.getMessage());
				}
				try {
					// if the string constructor is not null, we use it.
					// if the string constructor is null, we use the empty
					// constructor to get a new instance
					if (strCon != null) {
						testclass = strCon.newInstance(strParams);
					} else if (con != null) {
						testclass = con.newInstance(params);
					}
				} catch (InvocationTargetException e) {
					log.warn(e.getMessage());
				} catch (InstantiationException e) {
					log.info(e.getMessage());
				} catch (IllegalAccessException e) {
					log.info(e.getMessage());
				}
			}
			//为了区分不同的case，将线程名字更改为第一个传入参数的值。itest第一个参数值为case路径，其它代码可将其设置为区分case的名称
			String threadName = (String)params[0];
			Thread.currentThread().setName(threadName);
		}
		return testclass;
	}
	
	private static int getInstanceNum(String className) {
		Class<?> theclazz = null;
		int instanceNum = 0;
		Object[] params = null;
		try {
			theclazz = Thread.currentThread().getContextClassLoader()
					.loadClass(className.trim());
		} catch (ClassNotFoundException e) {
			log.warn("ClassNotFoundException:: " + e.getMessage());
		}
		if (theclazz != null) {
			Method[] theMethods = theclazz.getMethods();
			for (Method m : theMethods) {
				if (m.getReturnType().isAssignableFrom(
						java.util.Collection.class)) {
					Collection<Object[]> returnParam;
					try {
						returnParam = (Collection<Object[]>) m.invoke(null);
						instanceNum = returnParam.size();
						break;
					} catch (IllegalArgumentException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}
			}
		}
		return instanceNum;
	}
	
	
	private class AnnotatedTestCase extends TestCase {
		private final Method method;
		private final Class<? extends Throwable> expectedException;
		private final long timeout;

		public AnnotatedTestCase(Method method,
				Class<? extends Throwable> expectedException2, long timeout) {
			this.method = method;
			this.expectedException = expectedException2;
			this.timeout = timeout;
		}

		@Override
		protected void runTest() throws Throwable {
			try {
				long start = System.currentTimeMillis();
				// 增加instanceIndex，运行指定的实例
				method.invoke(testObjectList.get(instanceIndex), (Object[]) null);
				if (expectedException != None.class) {
					throw new AssertionFailedError(
							"No error was generated for a test case which specifies an error.");
				}
				if (timeout > 0) {
					long elapsed = System.currentTimeMillis() - start;
					if (elapsed > timeout) {
						throw new AssertionFailedError(
								"Test took longer than the specified timeout.");
					}
				}
			} catch (InvocationTargetException e) {
				Throwable thrown = e.getCause();
				if (thrown == null) { // probably should not happen
					throw e;
				}
				if (expectedException == None.class) {
					// Convert JUnit4 AssertionError failures to JUnit3 style so
					// will be treated as failure rather than error.
					if (thrown instanceof AssertionError
							&& !(thrown instanceof AssertionFailedError)) {
						AssertionFailedError afe = new AssertionFailedError(
								thrown.toString());
						// copy the original stack trace
						afe.setStackTrace(thrown.getStackTrace());
						throw afe;
					}
					throw thrown;
				}
				if (!expectedException.isAssignableFrom(thrown.getClass())) {
					throw new AssertionFailedError(
							"The wrong exception was thrown from the test case");
				}
			}
		}
	}

	public void threadFinished() {
	}
	
	/**
	 * Set up all variables that don't change between samples.
	 */
	@Override
	public void threadStarted() {
		testObject = null;
		testObjectList = new ArrayList<Object>();
		testCase = null;
		methodName = getMethod();
		className = getClassname();
		
		protectable = null;
		
		String rlabel = getConstructorString();
		if (rlabel.length() == 0) {
			rlabel = JUnitSampler.class.getName();
		}
		// 判断有多少组参数
		this.instanceNum = getInstanceNum(className);
		// 依次初始化线程
		for (this.instanceIndex = 0; this.instanceIndex < this.instanceNum; this.instanceIndex++) {
			final Method m;
		testObject= getClassInstance(className, rlabel,this.instanceIndex);
		testObjectList.add(instanceIndex, testObject);
		runProtected(testObjectList.get(instanceIndex), methodName);
		}
	}
	
	// 此处有代码重构，但case运行方式未做改变
	protected void runProtected(final Object test, final String methodName) {
		final Method m;
		if (test!= null) {
			initMethodObjects(test);
			m = getMethod(test, methodName);
			if (getJunit4()) {
				Class<? extends Throwable> expectedException = None.class;
				long timeout = 0;
				Test annotation = m.getAnnotation(Test.class);
				if (null != annotation) {
					expectedException = annotation.expected();
					timeout = annotation.timeout();
				}
				final AnnotatedTestCase at = new AnnotatedTestCase(m,
						expectedException, timeout);
				testCase = at;
				testCases.add(testCase);
				protectable = new Protectable() {
					public void protect() throws Throwable {
						at.runTest();
					}
				};
			} else {
//				this.testCase = (TestCase) this.testObject;
				final Object theClazz = test; // Must be final to
															// create instance
				protectable = new Protectable() {
					public void protect() throws Throwable {
						try {
							m.invoke(theClazz, new Object[0]);
						} catch (InvocationTargetException e) {
							/*
							 * Calling a method via reflection results in
							 * wrapping any Exceptions in ITE; unwrap these here
							 * so runProtected can allocate them correctly.
							 */
							Throwable t = e.getCause();
							if (t != null) {
								throw t;
							}
							throw e;
						}
					}
				};
			}
			if (this.testCase != null) {
//				TODO
				// 将testCase的名字设置为当前线程名，用来区分不同组变量的case
				String threadName = Thread.currentThread().getName();
				this.testCase.setName(methodName+": "+threadName);
			}
		}
	}

}
